////////////////////////////////////////////////////////////////////////////////////////////////////
#include "Commons.h"
#include "CRotationControl.h"
#include "CUserInterface.h"
#include "CLevelEditor.h"
#include "CEventReceiver.h"
#include "DoUndoCommands.h"
#include "ISceneCollisionManager.h"
#include "CMoveCommand.h"
#include "CRotateCommand.h"
#include "CMultiCommand.h"
#include "ISceneManager.h"
#include "ISceneNode.h"
#include "ICameraSceneNode.h"
#include "SViewFrustum.h"
#include "math.h"
#include "IrrlichtDevice.h"
////////////////////////////////////////////////////////////////////////////////////////////////////

extern CGlobals* g;
extern CUserInterface* ui;
extern CLevelEditor* editor;

////////////////////////////////////////////////////////////////////////////////////////////////////

void drawThick2DLine (position2di p1, position2di p2, int thickness, SColor color);
void drawThick2DLine (vector2df p1, vector2df p2, int thickness, SColor color);
void drawThick2DArrow (vector2df p1, vector2df p2, int thickness, SColor color);

////////////////////////////////////////////////////////////////////////////////////////////////////

void getNewPositionAndRotation (vector3df pivot, quaternion rot,
								vector3df curPos, quaternion curRot,
								vector3df* newPos, quaternion* newRot)
{
	matrix4 translateToPivot;
	translateToPivot.setTranslation(-pivot);
	matrix4 translateFromPivot;
	translateFromPivot.setTranslation(pivot);
	matrix4 translateFromOrigin;
	translateFromOrigin.setTranslation(curPos);

	matrix4 transform = translateFromPivot * rot.getMatrix() * translateToPivot * translateFromOrigin * curRot.getMatrix();

	*newPos = transform.getTranslation();
	*newRot = quaternion(transform);
}
////////////////////////////////////////////////////////////////////////////////////////////////////

CRotationControl::CRotationControl (array<ISceneNode*> nodes, vector3df axis1, vector3df axis2, SColor normalColor, SColor highlightColor) 
{
	m_type = E_ROTATION;
	m_nodes = nodes;
	m_nodesStartPos.set_used(m_nodes.size());
	m_nodesStartRot.set_used(m_nodes.size());
	m_axis1 = axis1;
	m_axis2 = axis2;
	m_normalColor = normalColor;
	m_highlightColor = highlightColor;
	m_activated = false;
	m_highlighted = false;
	m_centroid = vector3df();
	m_absCentroid = vector3df();
	for (u32 i = 0; i < m_nodes.size(); ++i)
	{
		m_centroid += m_nodes[i]->getPosition();
		m_absCentroid += m_nodes[i]->getAbsolutePosition();
	}
	m_centroid /= (f32)m_nodes.size();
	m_absCentroid /= (f32)m_nodes.size();
}
////////////////////////////////////////////////////////////////////////////////////////////////////

CRotationControl::~CRotationControl ()
{
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CRotationControl::OnEvent (const SEvent& event)
{
	switch (event.EventType)
	{
	case EET_MOUSE_INPUT_EVENT:
		return onMouseEvent(event.MouseInput);
	}
	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

float pointToSegmentDistance (vector2df pt, vector2df s1, vector2df s2);

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CRotationControl::isUsable () const
{
	vector3df dir = m_absCentroid - g->sceneManager->getActiveCamera()->getPosition(); dir.normalize();
	vector3df normal = m_axis1.crossProduct(m_axis2); normal.normalize();

	return ABS(dir.dotProduct(normal)) >= 0.1;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CRotationControl::isPointOverControl (position2di pt) const
{
	if (!isUsable()) return false;

	array<vector2df> coords;
	getScreenCoordinates(&coords);

	vector2df ptf(f32(pt.X), f32(pt.Y));
	for (u32 i = 0; i < coords.size(); ++i)
	{
		u32 j = (i + 1) % coords.size();
		if (pointToSegmentDistance(ptf, coords[i], coords[j]) <= 3.0f)
			return true;
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

bool project3DTo2D (vector3df pt, vector2df* result, ICameraSceneNode* camera = 0);

////////////////////////////////////////////////////////////////////////////////////////////////////

bool CRotationControl::onMouseEvent (const SEvent::SMouseInput& event)
{
	position2di mouse(event.X, event.Y);

	if (editor->isPointOverGUI(mouse))
		return false;

	bool mouseOverControl = isPointOverControl(mouse);

	m_highlighted = mouseOverControl && !g->eventReceiver->IsKeyDown(KEY_CONTROL);

	switch (event.Event)
	{
	case EMIE_LMOUSE_PRESSED_DOWN:
		if (mouseOverControl && !g->eventReceiver->IsKeyDown(KEY_CONTROL))
		{
			m_activated = true;
			g->eventReceiver->GrabAllEvents(this);

			for (u32 i = 0; i < m_nodes.size(); ++i)
			{
				m_nodesStartPos[i] = m_nodes[i]->getPosition();
				m_nodesStartRot[i] = m_nodes[i]->getRotation();
			}

			plane3df pl(m_absCentroid, m_axis1.crossProduct(m_axis2));
			line3df ray = g->sceneManager->getSceneCollisionManager()->getRayFromScreenCoordinates(mouse);
			pl.getIntersectionWithLine(ray.start, ray.getVector(), m_mouseStartPos);

			return true;
		}
		break;

	case EMIE_LMOUSE_LEFT_UP:
		if (m_activated)
		{
			m_activated = false;
			g->eventReceiver->ReleaseGrab(this);
			CMultiCommand* command = new CMultiCommand;
			for (u32 i = 0; i < m_nodes.size(); ++i)
			{
				vector3df newPos = m_nodes[i]->getPosition();
				quaternion newRot = m_nodes[i]->getRotation();
				CMoveCommand* move = new CMoveCommand(m_nodes[i]->getID(), m_nodesStartPos[i], newPos);
				CRotateCommand* rotate = new CRotateCommand(m_nodes[i]->getID(), m_nodesStartRot[i], newRot);
				command->addCommand(move);
				command->addCommand(rotate);
				move->drop();
				rotate->drop();
				if (i == 0) command->setDescription(rotate->getDescription());
			}
			if (m_nodes.size() > 1)
				command->setDescription(stringw(L"rotate ") + stringw(m_nodes.size()) + " objects");
			editor->AddCommand(command);

			return true;
		}
		break;
	}

	if (m_activated)
	{
		vector3df nodePos = m_absCentroid;

		position2di mouse = g->device->getCursorControl()->getPosition();
		plane3df pl(nodePos, m_axis1.crossProduct(m_axis2));
		line3df ray = g->sceneManager->getSceneCollisionManager()->getRayFromScreenCoordinates(mouse);
		vector3df rotateTo;
		pl.getIntersectionWithLine(ray.start, ray.getVector(), rotateTo);

		vector3df v1 = m_mouseStartPos - nodePos;
		vector3df v2 = rotateTo - nodePos;
		v1.normalize();
		v2.normalize();

		quaternion q;
		vector3df axis = -v1.crossProduct(v2);
		float cosangle = v1.dotProduct(v2);
		float sinangle = axis.getLength();
		if (-1e-6f <= sinangle && sinangle <= 1e-6f)
			axis = vector3df(1,0,0);
		else
			axis.normalize();

		q.fromAngleAxis(atan2(sinangle, cosangle), axis);

		for (u32 i = 0; i < m_nodes.size(); ++i)
		{
			vector3df newPos;
			quaternion newRot;
			getNewPositionAndRotation(m_centroid, q, m_nodesStartPos[i], m_nodesStartRot[i], &newPos, &newRot);

			core::vector3df rotate;
			newRot.toEuler(rotate);
			m_nodes[i]->setRotation(rotate);
			m_nodes[i]->setPosition(newPos);
		}

		ui->updateProperties();
	}

	return false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void CRotationControl::draw ()
{
	SColor color = (m_highlighted || m_activated) ? m_highlightColor : m_normalColor;
	if (m_activated)
	{
		plane3df pl2 = g->sceneManager->getActiveCamera()->getViewFrustum()->planes[SViewFrustum::VF_NEAR_PLANE];
		float dist = -pl2.getDistanceTo(m_absCentroid);

		float scale = 1;
		if (dist > 1.0f)
		{
			scale = dist/10.0f;
		}

		vector3df nodePos = m_absCentroid;

		position2di mouse = g->device->getCursorControl()->getPosition();
		plane3df pl(nodePos, m_axis1.crossProduct(m_axis2));
		line3df ray = g->sceneManager->getSceneCollisionManager()->getRayFromScreenCoordinates(mouse);
		vector3df rotateTo;
		pl.getIntersectionWithLine(ray.start, ray.getVector(), rotateTo);

		vector3df v1 = m_mouseStartPos - nodePos;
		vector3df v2 = rotateTo - nodePos;
		v1.normalize();
		v2.normalize();

		quaternion id, q;
		vector3df axis = -v1.crossProduct(v2);
		float cosangle = v1.dotProduct(v2);
		float sinangle = axis.getLength();
		if (-1e-6f <= sinangle && sinangle <= 1e-6f)
			axis = vector3df(1,0,0);
		else
			axis.normalize();
		float angle = atan2(sinangle, cosangle);
		q.fromAngleAxis(angle, axis);

		vector3df prev;
		for (int i = 0; i < 32; ++i)
		{
			quaternion q1;
			q1.slerp(id, q, i/31.0f);
			vector3df v = v1;
			q1.getMatrix().transformVect(v);

			vector2df start,end;
			project3DTo2D(nodePos + scale*prev, &start);
			project3DTo2D(nodePos + scale*v, &end);
			drawThick2DLine(start, end, i == 0 ? 1 : 2, color);
			prev = v;
		}

		vector2df start,end;
		project3DTo2D(nodePos + scale*prev, &start);
		project3DTo2D(nodePos, &end);
		drawThick2DLine(start, end, 1, color);
	}
	else
	{
		array<vector2df> coords;
		getScreenCoordinates(&coords);
		for (u32 i = 0; i < coords.size(); ++i)
		{
			u32 j = (i + 1) % coords.size();
			drawThick2DLine(coords[i], coords[j], 2, color);
		}
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////

void CRotationControl::getScreenCoordinates (array<vector2df>* coords) const
{
	vector3df nodePos = m_absCentroid;
	plane3df pl = g->sceneManager->getActiveCamera()->getViewFrustum()->planes[SViewFrustum::VF_NEAR_PLANE];
	float dist = -pl.getDistanceTo(nodePos);

	float scale = 1;
	if (dist > 1.0f)
	{
		scale = dist/10.0f;
	}

	int pts = 32;
	for (int i = 0; i < pts; ++i)
	{
		vector3df pt = nodePos + scale*cos(2*PI*i/pts)*m_axis1 + scale*sin(2*PI*i/pts)*m_axis2;
		vector2df proj;
		if (project3DTo2D(pt, &proj)) coords->push_back(proj);
	}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
